package com.aleyn.router

import androidx.collection.SimpleArrayMap
import androidx.fragment.app.Fragment
import java.lang.reflect.InvocationTargetException

/**
 * @author: Aleyn
 * @date: 2023/7/10 15:40
 */

/**
 * Fragment 工厂，用于创建和加载 Fragment 实例。
 * 这个文件包含了一系列辅助函数，通过反射机制来处理 Fragment 的实例化和类加载，
 * 并提供了缓存机制以优化性能。
 */

/**
 * 根据给定的类名创建一个新的 Fragment 实例。默认情况下，此方法使用
 * [loadFragmentClass] 加载 Fragment 类，并调用其无参构造函数。
 *
 * @param className 要实例化的 Fragment 的类名。
 * @return 返回一个新的 Fragment 实例。
 * @throws Fragment.InstantiationException 如果在实例化给定的 Fragment 类时失败。
 *         这是一个运行时异常，通常不应发生。
 */
internal fun instantiate(className: String): Fragment {
    return try {
        val cls = loadFragmentClass(className)
        cls.getConstructor().newInstance()!!
    } catch (e: InstantiationException) {
        throw Fragment.InstantiationException(
            "无法实例化 Fragment " + className
                    + ": 请确保类名存在、是 public 的，并且有一个 public 的无参构造函数", e
        )
    } catch (e: IllegalAccessException) {
        throw Fragment.InstantiationException(
            "无法实例化 Fragment " + className
                    + ": 请确保类名存在、是 public 的，并且有一个 public 的无参构造函数", e
        )
    } catch (e: NoSuchMethodException) {
        throw Fragment.InstantiationException(
            "无法实例化 Fragment " + className
                    + ": 找不到 Fragment 的构造函数", e
        )
    } catch (e: InvocationTargetException) {
        throw Fragment.InstantiationException(
            "无法实例化 Fragment " + className
                    + ": 调用 Fragment 构造函数时引发异常", e
        )
    }
}

/**
 * 用于缓存已加载的 Class 对象的全局映射。
 * 键是 ClassLoader，值是另一个 SimpleArrayMap，其键是类名，值是 Class 对象。
 * 这样设计是为了在不同的 ClassLoader 环境下隔离缓存。
 */
private val sClassCacheMap = SimpleArrayMap<ClassLoader, SimpleArrayMap<String, Class<*>>>()

/**
 * 从给定的类名加载 Class 对象。该方法会利用 [sClassCacheMap] 缓存，
 * 避免重复调用 [Class.forName]，从而提高性能。
 *
 * @param className 要加载的类的名称。
 * @return 返回加载的 Class 对象。
 * @throws ClassNotFoundException 如果找不到指定的类。
 */
@Throws(ClassNotFoundException::class)
internal fun loadClass(
    className: String
): Class<*> {
    val classLoader = Fragment::class.java.classLoader
    var classMap = sClassCacheMap[classLoader]
    if (classMap == null) {
        classMap = SimpleArrayMap()
        sClassCacheMap.put(classLoader, classMap)
    }
    var clazz = classMap[className]
    if (clazz == null) {
        // 如果缓存中不存在，则通过反射加载，并存入缓存
        clazz = Class.forName(className, false, classLoader)
        classMap.put(className, clazz)
    }
    return clazz!!
}

/**
 * 检查给定的类名是否是一个有效的 Fragment 类。
 *
 * @param className 要测试的 Fragment 的类名。
 * @return 如果 `className` 是 `androidx.fragment.app.Fragment` 或其子类，则返回 `true`，否则返回 `false`。
 */
internal fun isFragmentClass(
    className: String
): Boolean {
    return try {
        val clazz = loadClass(className)
        Fragment::class.java.isAssignableFrom(clazz)
    } catch (e: ClassNotFoundException) {
        false
    }
}

/**
 * 从给定的类名中解析 Fragment 的 Class 对象。解析结果会保存在全局缓存中，
 * 当再次传递相同的类名时，可以绕过 [Class.forName] 调用。
 *
 * @param className 要解析的 Fragment 的类名。
 * @return 返回解析后的 Fragment Class 对象。
 * @throws Fragment.InstantiationException 如果解析给定的 Fragment 类失败。
 *         这是一个运行时异常，通常不应发生。
 */
@Suppress("UNCHECKED_CAST")
internal fun loadFragmentClass(
    className: String
): Class<out Fragment?> {
    return try {
        loadClass(className) as Class<out Fragment?>
    } catch (e: ClassNotFoundException) {
        throw Fragment.InstantiationException(
            "无法实例化 Fragment " + className
                    + ": 请确保类名存在", e
        )
    } catch (e: ClassCastException) {
        throw Fragment.InstantiationException(
            "无法实例化 Fragment " + className
                    + ": 请确保该类是 Fragment 的有效子类", e
        )
    }
}